/***************************************************************************
 *   Copyright (C) 2007 by Ziman & Xloverin                                *
 *   ziman@centrum.sk, xloverin@gmail.com                                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "core.h"
#include "gossipquery.h"

#include <QMessageBox>
#include <stdexcept>
#include <exception>
#include <QSqlQuery>
#include <QSqlError>

void Core::displayGossipQuery()
{
	// Najviac jedna GossipQuery
	for(Queries::iterator it = queries.begin(); it != queries.end(); ++it)
		if (qobject_cast<GossipQuery *>(*it) != NULL)
			return;
	
	Query::Prefs prefs;
	prefs.acceptRE = "\x08";
	prefs.title = "Gossip";
	prefs.prefix = ".gossip ";
	prefs.filterMatching = true;
	
	Terminal *terminal = new Terminal();
	terminal->setFont(m_preferences.appearance.termFontFace, m_preferences.appearance.termFontSize);
	
	Query *query = new GossipQuery(prefs, terminal, this);
	
	mainForm->addTerminal(terminal, prefs.title);

	queries.insert(query);
}

TagCache &Core::tagCache()
{
	return *m_tagCache;
}

void Core::setAfk(bool afk)
{
	m_afk = afk;
	mainForm->onAfk(afk);
}

bool Core::afk()
{
	return m_afk;
}

QDateTime Core::loginTime()
{
	return m_loginTime;
}

QDateTime Core::lastActTime()
{
	return m_lastActTime;
}

void Core::deleteQuery(Query *query)
{
	queries.remove(query);
	delete query;
}

void Core::saveCurrentQueriesAsDefault()
{
	bool gossipFound = false;
	PrefsQueries::QueryList list;
	for (Queries::const_iterator it = queries.begin(); it != queries.end(); ++it) {
		if (qobject_cast<GossipQuery *>(*it) != NULL) {
			gossipFound = true;
			continue;
		}
		if ((*it) == m_mainQuery) continue;
		list.append((*it)->prefs());
	}
	saveDefaultQueries(list);
}

bool Core::connected()
{
	return connection->active();
}

PrefsQueries::QueryList Core::loadDefaultQueries()
{
	PrefsQueries::QueryList queryList;

	QSqlQuery query;
	query.exec(
		"SELECT nick, title, incoming, outgoing, prefix, filter_matching, accept_re, subject "
		"FROM default_queries ORDER BY id ASC"
	);

	while (query.next()) {
		Query::Prefs prefs;
		prefs.nick = query.value(0).toString();
		prefs.title = query.value(1).toString();
		prefs.incoming = query.value(2).toString();
		prefs.outgoing = query.value(3).toString();
		prefs.prefix = query.value(4).toString();
		prefs.filterMatching = query.value(5).toBool();
		prefs.acceptRE = query.value(6).toString();
		prefs.subject = query.value(7).toString();

		queryList.append(prefs);
	}

	return queryList;
}

NickList &Core::nickList()
{
	return *m_nickList;
}

void Core::saveDefaultQueries(PrefsQueries::QueryList queryList)
{
	QSqlQuery query;
	query.exec("DELETE FROM default_queries");

	for (PrefsQueries::QueryList::const_iterator it = queryList.begin(); it != queryList.end(); ++it) {
		query.prepare(
			"INSERT INTO default_queries "
			"(nick, title, accept_re, filter_matching, incoming, outgoing, prefix, subject) "
			"VALUES "
			"(:nick, :title, :accept_re, :filter_matching, :incoming, :outgoing, :prefix, :subject)"
		);
		query.bindValue(":nick", it->nick);
		query.bindValue(":title", it->title);
		query.bindValue(":accept_re", it->acceptRE);
		query.bindValue(":filter_matching", it->filterMatching);
		query.bindValue(":incoming", it->incoming);
		query.bindValue(":outgoing", it->outgoing);
		query.bindValue(":prefix", it->prefix);
		query.bindValue(":subject", it->subject);
		query.exec();
	}
}

void Core::setPreferences(Preferences::Prefs prefs)
{
	// General

	// Connection

	// Appearance
	mainForm->setTabPosition(prefs.appearance.tabPosition);
	for (Queries::const_iterator it = queries.begin(); it != queries.end(); ++it) {
		(*it)->terminal()->setFont(prefs.appearance.termFontFace, prefs.appearance.termFontSize);
	}

	// Queries

	// Log
	m_log->setPrefs(prefs.log);

	// Save
	m_preferences = prefs;
}

void Core::savePreferences(Preferences::Prefs prefs)
{
	// General
	config->set("general.nickList", prefs.general.nickList);
	config->set("general.tags", prefs.general.tags);

	// Connection
	config->set("connection.connectAuto", prefs.connection.connectAuto);
	config->set("connection.host", prefs.connection.host);
	config->set("connection.port", prefs.connection.port);
	config->set("connection.autoLogin", prefs.connection.autoLogin);
	config->set("connection.autoLogin.nick", prefs.connection.alNick);
	config->set("connection.autoLogin.pass", prefs.connection.alPassword);

	// Appearance
	config->set("appearance.tabPosition", (int) prefs.appearance.tabPosition);
	config->set("appearance.termFontSize", prefs.appearance.termFontSize);
	config->set("appearance.termFontFace", prefs.appearance.termFontFace);
	config->set("appearance.showTimestamps", prefs.appearance.showTimestamps);
	config->set("appearance.timestampFormat", prefs.appearance.timestampFormat);
	config->set("appearance.showTags", prefs.appearance.showTags);

	// Queries
	saveDefaultQueries(prefs.queries.queryList);
	config->set("queries.openDefaultQueriesAtStartup", prefs.queries.openDefaultQueriesAtStartup);
	config->set("queries.saveOpenQueriesAsDefaultOnQuit", prefs.queries.saveOpenAsDefaultOnQuit);
	config->set("queries.openGossipAtStartup", prefs.queries.openGossipAtStartup);

	// Log
	config->set("log.fileName", prefs.log.fileName);
	config->set("log.timestampEnabled", prefs.log.timestampEnabled);
	config->set("log.timestamp", prefs.log.timestamp);
	config->set("log.format", prefs.log.format);
	config->set("log.fnFormatSelected", prefs.log.fnFormatSelected);
}

const Preferences::Prefs &Core::preferences()
{
	return m_preferences;
}

Preferences::Prefs Core::loadPreferences()
{
	Preferences::Prefs prefs;

	// General
	prefs.general.nickList = config->get("general.nickList", "http://blackhole.sk/~ziman/spiral/nick_list.txt");
	prefs.general.tags = config->get("general.tags", "http://blackhole.sk/~ziman/spiral/tag_list.txt");

	// Connection
	prefs.connection.connectAuto = config->get("connection.connectAuto", true);
	prefs.connection.host = config->get("connection.host", "talker.sk");
	prefs.connection.port = config->get("connection.port", 23);
	prefs.connection.autoLogin = config->get("connection.autoLogin", true);
	prefs.connection.alNick = config->get("connection.autoLogin.nick", "");
	prefs.connection.alPassword = config->get("connection.autoLogin.pass", "");

	// Appearance
	prefs.appearance.tabPosition = (QTabWidget::TabPosition) config->get("appearance.tabPosition", (int) QTabWidget::South);
	prefs.appearance.termFontSize = config->get("appearance.termFontSize", 10);
	prefs.appearance.termFontFace = config->get("appearance.termFontFace", "Courier New");
	prefs.appearance.timestampFormat = config->get("appearance.timestampFormat", "%H:%M:%S");
	prefs.appearance.showTimestamps = config->get("appearance.showTimestamps", false);
	prefs.appearance.showTags = config->get("appearance.showTags", false);

	// Queries
	prefs.queries.queryList = loadDefaultQueries();
	prefs.queries.openDefaultQueriesAtStartup = config->get("queries.openDefaultQueriesAtStartup", true);
	prefs.queries.saveOpenAsDefaultOnQuit = config->get("queries.saveOpenQueriesAsDefaultOnQuit", false);
	prefs.queries.openGossipAtStartup = config->get("queries.openGossipAtStartup", false);

	// Log
	prefs.log.fileName = config->get("log.fileName", "log/%q_%y_%m");
	prefs.log.timestampEnabled = config->get("log.timestampEnabled", true);
	prefs.log.timestamp = config->get("log.timestamp", "%H:%M:%S");
	prefs.log.format = config->get("log.format", 1);
	prefs.log.fnFormatSelected = config->get("log.fnFormatSelected", 3);

	return prefs;
}

void Core::setHilightLevel(Query *query, Query::HilightLevel level)
{
	if (mainForm->activeTerminal() == query->terminal()) {
		level = Query::Inactive;
	}

	mainForm->setTerminalIcon(query->terminal(), level);
}

void Core::openDefaultQueries()
{
	PrefsQueries::QueryList queryList = loadDefaultQueries();
	for (PrefsQueries::QueryList::const_iterator it = queryList.begin(); it != queryList.end(); ++it) {
		addQuery(*it);
	}
	
	if (m_preferences.queries.openGossipAtStartup) {
		displayGossipQuery();
	}
}

void Core::setInputCapture(Query *inputCapture)
{
	m_inputCapture = inputCapture;
}

Query *Core::inputCapture()
{
	return m_inputCapture;
}

Query *Core::addQuery(Query::Prefs prefs, Terminal *terminal)
{
	if (!terminal) terminal = new Terminal();
	terminal->setFont(m_preferences.appearance.termFontFace, m_preferences.appearance.termFontSize);
	
	Query *query = new Query(prefs, terminal, this);
	
	mainForm->addTerminal(terminal, prefs.title);

	queries.insert(query);
	return query;
}

Config *Core::getConfig()
{
	return config;
}

Connection *Core::getConnection()
{
	return connection;
}

QSqlDatabase *Core::getDb()
{
	return &db;
}

void Core::on_connection_disconnected()
{
	m_loggedIn = false;
	m_nick = QString();
	mainForm->updateStatusbar();
}

void Core::on_connection_connected()
{
	setReadMode(rmChar);
}

void Core::connectTalker()
{
	connection->open(m_preferences.connection.host, m_preferences.connection.port);
}

void Core::disconnectTalker()
{
	connection->close();
}

void Core::setReadMode(Core::ReadMode mode)
{
	m_readMode = mode;
	mainForm->displayCharMode(mode == rmChar);
}

Core::ReadMode Core::readMode() const
{
	return m_readMode;
}

bool Core::macroDefined(QString name)
{
	return m_macroNames.contains(name);
}

void Core::refreshMacros()
{
	curCmd = cMacro;
	connection->write(".macro\r\n");
	m_macroNames.clear();
	qDebug("Macros deleted.");
}

bool Core::filterLine(const Line &line)
{
	if (line.tag().type == Line::Command && line.tag().subtype == CMD_EDITOR) {
		/* Vstup do znakoveho modu */
		setReadMode(rmChar);
		// uz tu nic nevypiseme, proste nam staci vratit false
		return false;
	}
	
	switch (curCmd) {
		case cNone:
			if (line.tag().type == Line::Afk) {
				if (!m_afk && line.tag().subtype == AFK_START) setAfk(true);
				if ( m_afk && line.tag().subtype == AFK_END) setAfk(false);
			}
			break;

		case cStatus:
			if (line.tag().type == Line::Command && line.tag().subtype == CMD_STATUS) {
				m_nick = line.tag().arg[0];
				m_loggedIn = true;
				m_loginTime = QDateTime::currentDateTime();
				m_lastActTime = m_loginTime;
				m_afk = false;
				emit login(m_nick);
				return false;
			}
			if (line.tag().type == Line::Command && line.tag().subtype == CMD_STATUS+1) {
				curCmd = cNone;
				
				// naparsuj makra
				refreshMacros();
				
				return false;
			}

			// Tento if je kvoli tomu, aby to nevyfiltrovalo .look este pred
			// rozpoznanim loginu
			if (m_loggedIn) return false;
			break;
			
		case cMacro: {
			if (line.tag().type == Line::Command && line.tag().subtype == CMD_MACRO+1) {
				curCmd = cNone;
			}
			
			if (line.tag().type == Line::Command && line.tag().subtype == CMD_MACRO) {
				qDebug("New macro: %s", line.tag().arg[0].toAscii().data());
				m_macroNames.insert(line.tag().arg[0]);
				
				/* workaround na viacstrankove makra */
				static int macroCount = 0;
				if (++macroCount == 10) { // enterujeme iba kazde desiate makro
					connection->write("\r\n");
					macroCount = 0;
				}
			}
			return false;
		}
		break;
	}

	return true;
}

void Core::on_connection_charDataReady(const QByteArray &data)
{
	// Mimo znakoveho modu zahodime data a prenechame to na riadkove spracovanie
	if (m_readMode != rmChar) return;
	
	m_mainQuery->terminal()->write(data, false);
}

void Core::onLineInCharMode(const QByteArray &data)
{
	// Tu nemozeme pouzit Line::tag(), pretoze sme v znakovom mode
	if (!m_loggedIn && data.contains("Lokacia: ")) {
		/* Skaredy (?) hack vyuzivajuci to, ze vzdy po prihlaseni sa automaticky spusti .look */
		setReadMode(rmLine);

		// Vypytame si status udaje
		connection->write(".status\r\n");
		curCmd = cStatus;
	}
	
	if (
			data.contains("Ziadny text")
		||	data.contains("Posta odoslana.")
		||	data.contains("Napisal si spravu na nastenku")
		||	data.contains("Napisala si spravu na nastenku")
		||	data.contains("Sprava bola zrusena")
	) {
		// Vstup do riadkoveho modu
		setReadMode(rmLine);
		
		// Toto by sa uz nevypisalo, pretoze sme uz v riadkovom mode
		m_mainQuery->terminal()->write(data, false);
	}
}

void Core::on_connection_lineDataReady(const QByteArray &data)
{
	if (m_readMode == rmChar) {
		onLineInCharMode(data);
		return;
	}
	
	Line line(data, m_tagCache);
			
	bool printInMain = true;
	bool lineMode = (m_readMode == rmLine);

	if (!filterLine(line)) return;
	
	Query *capture = m_inputCapture;
	if (capture && capture->accepts(line)) {
		/* Mame capturovany input */
		printInMain = (capture != m_mainQuery) && printInMain && !(capture->prefs().filterMatching);
		capture->processLine(line, lineMode);
	} else {
		/* Nemame capturovany input, prehladame vsetky query */

		m_inputCapture = 0;

		// if (lineMode)
		for (Queries::const_iterator it = queries.begin(); it != queries.end(); ++it) {
			Query *query = *it;
			if (query == m_mainQuery) continue;

			if (query->accepts(line)) {
				query->processLine(line, lineMode);
				printInMain = printInMain && !(query->prefs().filterMatching);

				// Volitelny break, tuna zrychluje, tak ho pouzijeme
				break;
			}
		}

	}

	// Vypiseme data na hlavny terminal
	if (printInMain) m_mainQuery->processLine(line, lineMode);
}

void Core::onUserActivity()
{
	m_lastActTime = QDateTime::currentDateTime();
}

bool Core::loggedIn()
{
	return m_loggedIn;
}

Log *Core::log()
{
	return m_log;
}

Core::Core(MainForm *mainForm)
{
	m_loggedIn = false;
	m_inputCapture = 0;
	curCmd = cNone;
	this->mainForm = mainForm;
	m_afk = false;
	m_log = new Log();
	m_readMode = rmChar;

	Query::Prefs prefs;
	prefs.filterMatching = false;
	prefs.title = tr("Main");
	m_mainQuery = addQuery(prefs, mainForm->mainTerminal());

	db = QSqlDatabase::addDatabase("QSQLITE");
	db.setDatabaseName("spiral.db");
	if (!db.open()) {
		QMessageBox::critical(mainForm, tr("Database connection error."), db.lastError().text());
		throw std::runtime_error("Could not open database.");
	}

	m_nickList = new NickList();
	config = new Config(&db);
	m_tagCache = new TagCache();
	connection = new Connection(this);

	connect(connection, SIGNAL(lineDataReady(const QByteArray &)), this, SLOT(on_connection_lineDataReady(const QByteArray &)));
	connect(connection, SIGNAL(charDataReady(const QByteArray &)), this, SLOT(on_connection_charDataReady(const QByteArray &)));
	connect(connection, SIGNAL(connected()), this, SLOT(on_connection_connected()));
	connect(connection, SIGNAL(disconnected()), this, SLOT(on_connection_disconnected()));

	// Aplikuje Preferences z databazy
	setPreferences(loadPreferences());

	// Natiahneme defaultne kvery
	if (m_preferences.queries.openDefaultQueriesAtStartup) openDefaultQueries();
}


Core::~Core()
{
	delete m_log;
	delete connection;
	delete m_tagCache;
	delete config;
	delete m_nickList;
	db.close();
}


